Skip to content

[pull] canary from vercel:canary - #1394

Merged
pull[bot] merged 9 commits into
code:canaryfrom
vercel:canary
Sep 14, 2026
Merged

pull[bot] merged 9 commits into
code:canaryfrom
vercel:canary

Conversation

@pull

@pull pull Bot commented Sep 14, 2026

Copy link
Copy Markdown

See Commits and Changes for more details.


Created by pull[bot] (v2.0.0-alpha.4)

Can you help keep this open source service alive? 💖 Please sponsor : )

mischnic and others added 9 commits September 14, 2026 07:43
…ing an owned string (#98520)

### What?

Follow-up to the review comments on #98497.

`new_atom` loses its generic parameter and takes a `Cow<'_, str>`, which
as a side effect stops it
copying a string it was handed ownership of. `FileSystemPath`'s
`get_relative_request_to` stops
hand-rolling a conversion the standard `From` impl already does, and the
two tests added for it in
`path.rs` are replaced by one that covers what that layer actually adds.

### Why?

@bgw noted that `get_relative_request_to` was matching on the `Cow` it
gets back only to call
`.into()` in both arms, and wondered whether `impl From<Cow<'_, str>>
for RcStr` was missing a fast
path for the owned case.

It was not missing one, and it never needed one — but an owned string
*was* being copied, for a
different reason. `new_atom` was generic over `T: AsRef<str> +
Into<String>` and opened with
`let text = text.as_ref();`, which shadows the owned value. From that
point on only a `&str` was in
scope, so building the `Box<str>` allocated a fresh buffer and copied
into it, dropping the `String`
that a `Cow::Owned` had just handed over.

So this is not a fast path being added. It is an accidental
pessimisation being removed: the generic
plus the `as_ref()` were exactly what stopped the standard conversion
from doing the right thing on
its own. Whether that copy ever mattered in practice is a separate
question, and one this does not
attempt to answer.

The other two comments were about the tests: they re-ran the case table
that already lives in
`turbo-unix-path`, which is where the path computation itself is
implemented and tested — "60 LOC to
test a 3 LOC branch".

### How?

`new_atom` takes a `Cow<'_, str>`. All five of its callers already
passed one, so nothing else
changes, and ownership now survives to where the `Box<str>` is built —
`Box<str>`'s own
`From<Cow<'_, str>>` moves an owned buffer in and copies only a borrowed
one. The inline-atom path is
untouched, since it reads through a `&str` either way and does not
allocate.

Contents-based assertions cannot distinguish a moved buffer from a
copied one, so the test compares
the data pointer before and after the conversion. It uses a string
longer than `MAX_INLINE_LEN`,
because an inline atom stores its bytes in the tagged value by design
and could never preserve a
pointer, and it asserts the atom really is dynamic so it cannot pass
vacuously if that boundary ever
moves. It fails on `canary` today.

`get_relative_request_to` collapses to a single `.into()`. Its sibling
`get_relative_path_to` keeps
its match on purpose: one arm uses `std::ptr::eq` to hand back a clone
of the `RcStr` the path
already holds, which the blanket conversion cannot do. That arm predates
#98497 and is an
optimisation rather than the redundancy that was flagged.

For the tests, `turbo-unix-path` remains the place where the relative
path computation is covered,
untouched. What is left here is only what this layer adds on top: that
each method reaches for the
form it names, and that neither returns a path across filesystems.
Keeping the first of those is a
judgement call — a method delegating to the wrong free function is
precisely the bug #98497 existed
to fix, and it is invisible to the lower-level table — so it is asserted
with one descendant pair
rather than a table. Happy to drop it entirely if you would rather.

To check that the smaller test still earns its place,
`get_relative_request_to` was temporarily
pointed at the plain-path function; the test fails as it should.

<!-- NEXT_JS_LLM -->

<!-- fleet 3acc7e63-52a8-4472-89cd-b1bbb8ad59fa -->

---------

Co-authored-by: vercel-fleet-prod[bot] <318278635+vercel-fleet-prod[bot]@users.noreply.github.com>
Co-authored-by: Tobias Koppers <1365881+sokra@users.noreply.github.com>
### Problem
1. Next computes the warmup cache key while `SOME_ENV_VAR` is unset.
2. Rendering ends up setting `SOME_ENV_VAR`.
3. Final prerender expects the cache entry to exist, but the env var
changed, so the key changed and it's a miss.
4. Unexpected miss and bailout.

This lead to
```
Error: Route "foo": Unexpected cache miss after cache warming phase during prerendering. This is
likely caused by non-deterministic arguments that differ between the cache warming phase and the
final prerender phase (e.g. unstable array order). Ensure that arguments passed to cached functions
are deterministic.

Error: Route "foo": Next.js encountered uncached or runtime data during
prerendering.
```

### Solution

~~Instead, snapshot the env vars once at module evaluation time of the
`use cache` function. This ensures that the cache key doesn't change
over time. This is also how it worked thus far (with deployment id as
the cache key): changing the env var over the lifetime of the process
didn't lead to a reexecution of the `use cache` function~~

RDC already stores cache entries generated in the previous phase (be it
during `next build` multi-phase rendering, or from prerender->resuming
at runtime). Root params are already excluded from the cache keys when
storing in RDC. Also exclude the env var hash bit, to conform to this
system of preventing tearing (at the cost of potential staleness).
### What?

Globally order all emitted Turbopack ECMAScript chunk items by module
path, including items nested inside batches. Browser and Node.js chunk
emitters now consume the same flattened, ordered result.

### Why?

The previous ordering compared only the first module in each item or
batch group. Remaining modules in a batch stayed together even when
their paths belonged elsewhere in the global order, reducing locality
between similar modules and slightly worsening gzip compression.

For `bench/basic-app`, production JavaScript chunk gzip sizes changed as
follows while raw bytes and chunk counts stayed unchanged:

| Output | Before | After | Change |
|---|---:|---:|---:|
| Browser chunks | 1,619,643 B | 1,619,314 B | -329 B (-0.020%) |
| Server chunks | 792,379 B | 792,186 B | -193 B (-0.024%) |
| Combined | 2,412,022 B | 2,411,500 B | -522 B (-0.022%) |

### How?

The shared ECMAScript chunk-content accessor now flattens resolved item
and batch groups and sorts every module by `(path, module id)`. Keeping
this ordering in the shared layer gives browser and Node.js output
identical deterministic behavior and removes duplicated emitter-side
sorting.

### Verification

- `cargo fmt --all -- --check`
- `cargo check -p turbopack-browser -p turbopack-nodejs`
- `cargo test -p turbopack-browser -p turbopack-nodejs`
- `pnpm build-all`
- Clean baseline and candidate production builds of `bench/basic-app`

<!-- NEXT_JS_LLM -->


<!-- fleet d30d9e3f-6885-4665-ae08-b37fa6151b95 -->

---------

Co-authored-by: vercel-fleet-prod[bot] <318278635+vercel-fleet-prod[bot]@users.noreply.github.com>
Co-authored-by: Tobias Koppers <1365881+sokra@users.noreply.github.com>
### What?

Reduce Turbopack ECMAScript chunk size by emitting strict module
factories in a shared strict-mode scope instead of repeating `"use
strict"` in every factory.

The chunk format now chooses among the existing flat representation, a
mixed representation with flat non-strict factories plus a nested strict
array, and an all-strict representation that wraps the complete chunk in
a strict IIFE. Each factory is generated through the existing single
code path; the production minimizer removes its redundant directive when
the factory is created inside a strict wrapper. Browser and Node.js
emitters share the mode selection and serialization implementation,
while the runtime remains compatible with the existing flat format.

### Why?

ECMAScript modules are always strict, so large chunks currently repeat
the same directive across many factories. Moving strictness to the scope
where those factories are created removes redundant bytes without
copying arrays or changing strict/sloppy execution semantics.

For `bench/basic-app`, this reduces total raw JavaScript by 16,831 bytes
(0.161%). Partitioning factories changes compression behavior, resulting
in a 1,286-byte (0.051%) gzip increase across all JavaScript; format
selection therefore remains based on emitted raw bytes.

### How?

- Determine each chunk item's strictness from the already-collected
chunk metadata.
- Generate every factory once with its normal directive, then rely on
the minimizer to remove redundant directives inside strict wrappers.
- Compare the expected minified directive bytes removed with the exact
wrapper bytes added and keep small chunks in the existing flat format.
- Keep non-strict factories flat in mixed chunks and append strict
factories as an array produced by a strict IIFE.
- Wrap the complete registration/export in a strict IIFE when every
factory is strict, keeping those factories flat and avoiding a nested
array entirely.
- Use the shorter arrow IIFE when the target supports it, with a
function IIFE fallback for older targets.
- Preserve source-map sections, scope-hoisted module IDs, factory
naming, and ordering within each partition.
- Add focused strict, sloppy, mixed, all-strict, old-target, and
below-threshold coverage and update affected Turbopack snapshots.

### Verification

- `cargo nextest run -p turbopack-tests -E 'test(snapshot)'`
- `cargo clippy -p turbopack-ecmascript -p turbopack-browser -p
turbopack-nodejs`
- `pnpm build-all`
- `pnpm --dir turbopack/crates/turbopack-ecmascript-runtime/js
check:nodejs`
- `bench/basic-app` production Turbopack build, repeated to confirm
deterministic size totals

<!-- NEXT_JS_LLM -->


<!-- fleet 528ffe69-eb27-466b-a132-842272a782cf -->

---------

Co-authored-by: vercel-fleet-prod[bot] <318278635+vercel-fleet-prod[bot]@users.noreply.github.com>
Co-authored-by: Tobias Koppers <1365881+sokra@users.noreply.github.com>
…pgrade (#98560)

### Why?

Async Request API codemods can leave temporary casts that still require
application-specific repairs. Those locations need explicit markers,
while optional feature adoption needs to be distinguishable from
required version migrations.

### How?

Add `@next-codemod-error` comments to temporary `UnsafeUnwrapped*`
casts, with instructions for completing the repair. Preserve documented
ignores and avoid duplicating casts or markers when the transform runs
again. Mark generated Cache Components opt-outs with
`@next-codemod-ignore`, a removal condition and a link to the adoption
guide.

Add `upgrade --skip-adoption` to skip the Cache Components `instant =
false` transform and partial-prefetch adoption cleanup. Required
migrations and normal dependency and bundler selection keep their
existing behavior. Both adoption transforms can still be run explicitly.
Stacked on #98560.

### Why?

Users need to distinguish unfinished codemod repairs from intentional
feature-adoption opt-outs.

### How?

Document `@next-codemod-error` and temporary `UnsafeUnwrapped*` casts in
the codemod guide, with examples and a link to the Async Request APIs
migration guide. Explain when an inapplicable suggestion can be marked
`@next-codemod-ignore` with a reason.

Show the ignore comment emitted for Cache Components opt-outs and
explain that it does not block compilation.
### What?

Prevent the OpenTelemetry instrumentation e2e collector from losing span
exports when an exporter reuses an idle localhost connection. Add
focused coverage for the collector's connection behavior.

### Why?

Keeping the collector listener alive for each application's full
lifetime removed the earlier close-and-rebind gap, but did not eliminate
every socket failure. Node's HTTP server could still time out an idle
keep-alive socket retained by the exporter's Undici pool. A later export
could race that closure, lose spans with `UND_ERR_SOCKET` or
`ECONNRESET`, and leave otherwise unrelated trace assertions with
incomplete trees.

The canary.28 failure was investigated separately and is not this
mechanism: it occurred in the release-enabled Rspack matrix when a
custom server exited during startup. This change is limited to the
residual non-Rspack collector race and does not alter Rspack coverage or
release snapshots.

### How?

Successful collector exports explicitly close their HTTP connection.
Each export therefore uses a fresh local connection instead of retaining
a socket that the server may reclaim while idle. The regression test
asserts this response contract directly, avoiding timing-dependent
coverage of the race.

### Verification

- Node 20.9.0, webpack dev, React 18.3.1: 5 consecutive focused runs, 55
tests passed per run
- Node 20.9.0, webpack start, React 18.3.1: 99 passed, 13 existing skips
- `pnpm test-types`
- Prettier and ESLint on changed files

<!-- NEXT_JS_LLM -->


<!-- fleet e4b8ed15-4a06-4727-a634-fddcc27c11fc -->

Co-authored-by: vercel-fleet-prod[bot] <318278635+vercel-fleet-prod[bot]@users.noreply.github.com>
Co-authored-by: Tobias Koppers <1365881+sokra@users.noreply.github.com>
@pull pull Bot locked and limited conversation to collaborators Sep 14, 2026
@pull pull Bot added the ⤵️ pull label Sep 14, 2026
@pull
pull Bot merged commit 3c9d1ca into code:canary Sep 14, 2026
4 of 6 checks passed
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants